<?php

/**
 * Class library для работы с субъектами безопасности
 *
 */
abstract class Security_Utils_Subject {

    /**
     * Код действия логина.
     *
     * @var integer
     */
    const ACTION_LOGIN = 0;

    /**
     * Проекция ролей субъекта безопасности на наименования таблиц, в которых
     * хранятся профайлы, соответствующие этим ролям.
     *
     * @var array
     */
    static private $_tables = array
    (
        Security_Subject::USER => 'user_user',
        Security_Subject::ADMIN => 'system_admin',
    );

    /**
     * Создает новый субъект безопасности и возвращает его идентификатор.
     *
     * @param   string  $email
     * @param   string  $password
     * @param   boolean $encrypted
     * @return  integer
     */
    static public function create($email, $password, $encrypted = false) {
        return database::insert('system_subject', array(
            'email' => $email,
            'password' => $encrypted ? $password : self::_encrypt($password),
        ));
    }

    /**
     * Сбрасывает пароль.
     *
     * @param   integer $account_id
     * @param   integer $role
     * @return  boolean
     */
    static public function reset($account_id, $role) {

        // определяем настройки длины пароля
        list($min, $max) = array_values(Kernel_Config::get('security', array('password_min_length', 'password_max_length')));

        // должен ли пароль быть произносимым
        $pronounceable = (boolean)Kernel_Config::get('security', 'password_pronounceable');

        // генерируем новый пароль для пользователя
        $password = Security_Utils_Password::generate(rand($min, $max), $pronounceable);

        // обновляем профайл пользователя
        return self::setPassword($account_id, $password)

            // отправляем пользователю сообщение
            && self::_send($account_id, 'new-password', $role, array('password' => $password));
    }

    /**
     * Обновляет пароль указанного субъекта безопасности.
     *
     * @param   integer  $account_id
     * @param   string   $password
     * @return  boolean
     */
    static public function setEmail($account_id, $email) {
        return database::update('system_subject', array(
            'email' => $email,
        ), (int)$account_id) > 0;
    }

    /**
     * Обновляет пароль указанного субъекта безопасности.
     *
     * @param   integer  $account_id
     * @param   string   $password
     * @return  boolean
     */
    static public function setPassword($account_id, $password) {
        return database::update('system_subject', array(
            'password' => self::_encrypt($password),
        ), (int)$account_id) > 0;
    }

    /**
     * Проверяет правильность пароля для указанного аккаунта. Используется
     * валидатором текущего пароля.
     *
     * @param   integer  $account_id
     * @param   string   $password
     * @return  boolean
     */
    static public function isPasswordValid($account_id, $password) {
        $query = 'SELECT
                         `password`
                    FROM
                         `system_subject`
                   WHERE
                         `id` = ' . (int)$account_id;

        return false !== ($encrypted = database::fetchOne($query))
            && self::compare($password, $encrypted);
    }

    /**
     * Возвращает субъект безопасности, соответствующий указанному профайлу.
     *
     * @param   integer $profile
     * @param   integer $role
     * @return  integer
     */
    static public function getSubjectByProfile($profile, $role) {
        $query = 'SELECT
                         `subject_id`
                    FROM
                         `' . self::$_tables[$role] . '`
                   WHERE
                         `id` = "' . (int)$profile . '"';
        return database::fetchOne($query);
    }

    /**
     * Возвращает идентификатор профайла указанной роли, связанного с указанным
     * субъектом безопасности.
     *
     * @param   mixed   $account_id
     * @param   integer $role
     * @return  integer
     */
    static public function getProfileId($account_id, $role) 
    {
        
        $query = 'SELECT
                         `p`.`id`
                    FROM
                         `' . self::$_tables[$role] . '` `p`
              INNER JOIN
                         `system_subject` `s`
                      ON
                         `s`.`id` = `p`.`subject_id`
                   WHERE';

        // если передан идентификатор аккаунта
        if (is_numeric($account_id)) {
            $query .= '
                         `s`.`id` = ' . (int)$account_id;
        } elseif (is_string($account_id)) {
            // строку трактуем как e-mail
            $query .= '
                         `s`.`email` = "' . database::escape($account_id) . '"';
        } else {
            trigger_error('Unsupporded argument type', E_USER_ERROR);
        }

        return database::fetchOne($query);
    }

    /**
     * Возвращает секретные вопросы для указанного субъекта безопасности.
     *
     * @param   integer $subject
     * @return  array
     */
    static public function getQuestions($subject) {
        $query = 'SELECT
                         `position`,
                         `question`,
                         `answer`
                    FROM
                         `system_subject_question`
                   WHERE
                         `subject_id` = ' . (int)$subject . '
                   LIMIT
                         ' . Kernel_Config::get('security', 'questions');

        return database::fetchAssoc($query);
    }

    /**
     * Возвращает доступные для использования секретные вопросы.
     *
     * @return  array
     */
    static public function getAvailableQuestions() {
        $query = 'SELECT
                         `id`,
                         `question`
                    FROM
                         `system_question`
                ORDER BY
                         `id`';

        return database::fetchPairs($query);
    }

    /**
     * Возвращает, определены ли секретные вопросы для субъекта безопасности c
     * указанным email.
     *
     * @param   string  $email
     * @return  boolean
     */
    static public function hasQuestions($email) {
        $query = 'SELECT
                         COUNT(*)
                    FROM
                         `system_subject_question` `q`
              INNER JOIN
                         `system_subject` `s`
                      ON
                         `s`.`id` = `q`.`subject_id`
                   WHERE
                         `s`.`email` = "' . database::escape($email) . '"';

        return database::fetchOne($query) > 0;
    }

    /**
     * Возвращает субъект безопасности, соответствующий указанному email.
     *
     * @param   string  $email
     * @return  integer
     */
    static public function getSubjectByEmail($email) {
        $query = 'SELECT
                         `id`
                    FROM
                         `system_subject` `s`
                   WHERE
                         `email` = "' . database::escape($email) . '"';

        return database::fetchOne($query);
    }

    /**
     * Сравнивает пароль, введенный пользователем с зашифрованным значением.
     *
     * @param   string  $password
     * @param   string  $encrypted
     * @return  string
     */
    static public function compare($password, $encrypted) {
        return self::_encryption()->compare($password, $encrypted);
    }

    /**
     * Шифрует указанный пароль.
     *
     * @param   string  $password
     * @return  string
     */
    static private function _encrypt($password) {
        return self::_encryption()->encrypt($password);
    }

    /**
     * Возвращает реализацию алгоритма шифрования, указанного в настройках проекта.
     *
     * @return  Security_Encryption_Interface
     */
    static private function _encryption() {
        static $instance;
        return isset($instance)
            ? $instance
            : $instance = Kernel::factory('Security_Encryption', Kernel_Config::get('security', 'encryption'));
    }

    /**
     * Отправляет почтовое уведомление.
     *
     * @param   integer $account_id
     * @param   integer $message
     * @param   array   $vars
     * @return  boolean
     */
    static private function _send($account_id, $action, $role, $vars = array()) {
        $_messages = array(
            Security_Subject::ADMIN => 'backend',
            Security_Subject::USER  => 'user',
        );

        $prefix = $_messages[$role];

        $class = ucfirst($prefix . '_Mail_Message');
        $name = $prefix . '/' . $action;

        $message = new $class(array(
            'name' => $name,
            'profile' => self::getProfileId($account_id, $role)
        ));

        return $message->assign($vars)->send();
    }
}
